package org.nstack.compute.cluster;

import org.nstack.core.workflow.FlowChainBuilder;
import org.nstack.header.cluster.*;
import org.nstack.header.core.workflow.*;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.nstack.core.cascade.CascadeConstant;
import org.nstack.core.cascade.CascadeFacade;
import org.nstack.core.cloudbus.CloudBus;
import org.nstack.core.db.DatabaseFacade;
import org.nstack.core.errorcode.ErrorFacade;
import org.nstack.header.core.NopeCompletion;
import org.nstack.header.errorcode.SysErrors;
import org.nstack.core.thread.SyncTask;
import org.nstack.core.thread.ThreadFacade;
import org.nstack.header.core.Completion;
import org.nstack.header.errorcode.ErrorCode;
import org.nstack.header.message.APIDeleteMessage;
import org.nstack.header.message.APIMessage;
import org.nstack.header.message.Message;
import org.nstack.utils.Utils;
import org.nstack.utils.logging.CLogger;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class ClusterBase extends AbstractCluster {
	protected final static CLogger logger = Utils.getLogger(ClusterBase.class);

	@Autowired
	protected DatabaseFacade dbf;
	@Autowired
	protected CloudBus bus;
	@Autowired
	protected ThreadFacade thdf;
	@Autowired
	protected ClusterExtensionPointEmitter extpEmitter;
    @Autowired
    protected CascadeFacade casf;
    @Autowired
    protected ErrorFacade errf;

	protected ClusterVO self;
	protected final int threadSyncLevel = 1;

	public ClusterBase(ClusterVO self) {
		this.self = self;
	}

	protected ClusterInventory changeState(ClusterStateEvent event) {
		ClusterState currentState = self.getState();
		ClusterState next = AbstractCluster.getNextState(self.getState(), event);

		extpEmitter.beforeChange(self, event);
		self.setState(next);
		self = dbf.updateAndRefresh(self);
		ClusterInventory inv = ClusterInventory.valueOf(self);
		extpEmitter.afterChange(self, event, currentState);
		logger.debug("Cluster " + self.getName() + " uuid: " + self.getUuid() + " changed state from " + currentState + " to " + self.getState());
		return inv;
	}

	protected void handleApiMessage(APIMessage msg) {
		if (msg.getClass() == APIChangeClusterStateMsg.class) {
			handle((APIChangeClusterStateMsg) msg);
		} else if (msg.getClass() == APIDeleteClusterMsg.class) {
			handle((APIDeleteClusterMsg) msg);
		} else if (msg instanceof APIUpdateClusterMsg) {
			handle((APIUpdateClusterMsg) msg);
		} else {
			bus.dealWithUnknownMessage(msg);
		}
	}

	private void handle(APIUpdateClusterMsg msg) {
		boolean update = false;
		if (msg.getName() != null) {
			self.setName(msg.getName());
			update = true;
		}
		if (msg.getDescription() != null) {
			self.setDescription(msg.getDescription());
			update = true;
		}
		if (update) {
			self = dbf.updateAndRefresh(self);
		}

		APIUpdateClusterEvent evt = new APIUpdateClusterEvent(msg.getId());
		evt.setInventory(ClusterInventory.valueOf(self));
		bus.publish(evt);
	}

	protected void handle(final APIDeleteClusterMsg msg) {
        final APIDeleteClusterEvent evt = new APIDeleteClusterEvent(msg.getId());
        final String issuer = ClusterVO.class.getSimpleName();
        final List<ClusterInventory> ctx = ClusterInventory.valueOf(Arrays.asList(self));
        FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
        chain.setName(String.format("delete-cluster-%s", msg.getUuid()));
        if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) {
            chain.then(new NoRollbackFlow() {
                @Override
                public void run(final FlowTrigger trigger, Map data) {
                    casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) {
                        @Override
                        public void success() {
                            trigger.next();
                        }

                        @Override
                        public void fail(ErrorCode errorCode) {
                            trigger.fail(errorCode);
                        }
                    });
                }
            }).then(new NoRollbackFlow() {
                @Override
                public void run(final FlowTrigger trigger, Map data) {
                    casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) {
                        @Override
                        public void success() {
                            trigger.next();
                        }

                        @Override
                        public void fail(ErrorCode errorCode) {
                            trigger.fail(errorCode);
                        }
                    });
                }
            });
        } else {
            chain.then(new NoRollbackFlow() {
                @Override
                public void run(final FlowTrigger trigger, Map data) {
                    casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) {
                        @Override
                        public void success() {
                            trigger.next();
                        }

                        @Override
                        public void fail(ErrorCode errorCode) {
                            trigger.fail(errorCode);
                        }
                    });
                }
            });
        }

        chain.done(new FlowDoneHandler(msg) {
            @Override
            public void handle(Map data) {
                casf.asyncCascadeFull(CascadeConstant.DELETION_CLEANUP_CODE, issuer, ctx, new NopeCompletion());
                bus.publish(evt);
            }
        }).error(new FlowErrorHandler(msg) {
            @Override
            public void handle(ErrorCode errCode, Map data) {
                evt.setError(errf.instantiateErrorCode(SysErrors.DELETE_RESOURCE_ERROR, errCode));
                bus.publish(evt);
            }
        }).start();
	}

	protected void changeStateByApiMsg(APIChangeClusterStateMsg msg) {
		APIChangeClusterStateEvent evt = new APIChangeClusterStateEvent(msg.getId());
		ClusterStateEvent stateEvent = ClusterStateEvent.valueOf(msg.getStateEvent());
        try {
            extpEmitter.preChange(self, stateEvent);
        } catch (ClusterException e) {
            evt.setError(errf.instantiateErrorCode(SysErrors.CHANGE_RESOURCE_STATE_ERROR, e.getMessage()));
            bus.publish(evt);
            return;
        }

		try {
			ClusterInventory inv = changeState(stateEvent);
			evt.setInventory(inv);
			bus.publish(evt);
		} catch (Exception e) {
			bus.logExceptionWithMessageDump(msg, e);
			bus.replyErrorByMessageType(msg, e);
		}

	}

	protected void handle(final APIChangeClusterStateMsg msg) {
		thdf.syncSubmit(new SyncTask<Void>() {
			@Override
			public String getName() {
				return "ChangeClusterStateByApi";
			}

			@Override
			public Void call() throws Exception {
				changeStateByApiMsg(msg);
				return null;
			}

			@Override
			public String getSyncSignature() {
				return self.getUuid();
			}

			@Override
			public int getSyncLevel() {
				return threadSyncLevel;
			}
		});
	}

	protected void handleLocalMessage(Message msg) {
		if (msg instanceof ChangeClusterStateMsg) {
			handle((ChangeClusterStateMsg) msg);
        } else if (msg instanceof ClusterDeletionMsg) {
            handle((ClusterDeletionMsg) msg);
		} else {
			bus.dealWithUnknownMessage(msg);
		}
	}

    private void handle(ClusterDeletionMsg msg) {
        ClusterInventory inv = ClusterInventory.valueOf(self);
        extpEmitter.beforeDelete(inv);
        deleteHook();
        extpEmitter.afterDelete(inv);

        ClusterDeletionReply reply = new ClusterDeletionReply();
        bus.reply(msg ,reply);
    }

    protected void changeClusterStateByLocalMsg(ChangeClusterStateMsg msg) {
		ChangeClusterStateReply reply = new ChangeClusterStateReply();
		try {
			ClusterInventory inv = changeState(ClusterStateEvent.valueOf(msg.getStateEvent()));
			reply.setInventory(inv);
			bus.reply(msg, reply);
		} catch (Exception e) {
			bus.logExceptionWithMessageDump(msg, e);
			bus.replyErrorByMessageType(msg, e);
		}

	}

	protected void handle(final ChangeClusterStateMsg msg) {
		thdf.syncSubmit(new SyncTask<Void>() {
			@Override
			public String getName() {
				return "ChangeClusterStateByLocalMessage";
			}

			@Override
			public Void call() throws Exception {
				changeClusterStateByLocalMsg(msg);
				return null;
			}

			@Override
			public String getSyncSignature() {
				return self.getUuid();
			}

			@Override
			public int getSyncLevel() {
				return threadSyncLevel;
			}
		});
	}

	@Override
	public void handleMessage(Message msg) {
        try {
            if (msg instanceof APIMessage) {
                handleApiMessage((APIMessage) msg);
            } else {
                handleLocalMessage(msg);
            }
        } catch (Exception e) {
            bus.logExceptionWithMessageDump(msg, e);
            bus.replyErrorByMessageType(msg ,e);
        }
	}

    @Override
    protected void deleteHook() {
    }
}
